﻿/*
 * Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.
 */

#include "BurnCtrlMcu.h"
#include <QBitArray>
#include <QProcessEnvironment>
#include "Burn.h"

namespace {
    const int DEFAULT_STOP_BIT = 0;
    const int DEFAULT_PORT = 4444;
    const int USB_DEVICES_VID = 0x1234;
    const int USB_DEVICES_PID = 0x5678;
    const int TIMER_OUT_OPEN = 30000;
    const int BYTE_LEN = 8;
    const BYTE GET_CHIPID_CMD = 0xB7;
    const BYTE UNPROTECTCMD = 0xF6;
    const BYTE CMD_UP_3066_FLASH = 0xF9;
    const int TIMER_OUT_FRAME = 10000;
    const int WAIT_DEVICE_CLOSE = 100;
    const int REOPEN_SERIAL_TIMEOUT = 500;
    const int TINER_OUT_GET_CHIPID = 300;
    const int ERASE_CMD_LEN = 9;
    const BYTE ERASECMD = 0xE4;
    const int UPLOAD_SLEEP = 20480;
    const int TIMER_OUT_WRITE_FILE = 1000;
    const int RUN_OFFSET_ADDRESS = 4;
    const int GO_CMD_LEN = 5;
    const QString JLINK_GO_CMD = "resume\r\n";
    const BYTE GOCMD = 0xBA;
    const BYTE RESETCMD = 0x87;
    const QString CONFIG_PATH = "\\config_mcu\\config\\config.ini";
    bool g_isCancel = 0;
    const int FLASH_128K = 128 * 1024;
    const int BIT_24 = 24;
    const int BIT_16 = 16;
    const int BIT_8 = 8;
    const int HEX = 16;
    const double PRO_CALL_BACK = 80;
    const double PRO_99 = 99;
    const double PRO_20 = 20;
    const double PERCENTAGE = 100;
    const int CARRY = 1024;
    using namespace BurnToolSpace;

}
QMap<unsigned long long, BurnCtrlMcu *> BurnCtrlMcu::myCtrlMap;
BurnCtrlMcu::BurnCtrlMcu(ChipType type, QObject *parent)
    : BurnCtrl(type, parent),
      deviceHander(0),
      isWriteInfo(false),
      isAllinone(false),
      loaderFile(""),
      loaderSize(0),
      totalDigits(0),
      parameterStartAddress(0),
      isInfoPart(false),
      eraseBaseNum(0),
      mutex(new QMutex())
{
    independent = true;
    qRegisterMetaType<QString>("QString");
    RegisterCallback(SetTextEditReceivePrint, CancelBurn, SetProgress);
    QString addpath = QApplication::applicationDirPath().replace("/", "\\");
    // 创建一个新的进程环境
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();

    // 获取当前的PATH环境变量
    QString path = env.value("Path"); // 注意：Windows系统中的环境变量名为"Path"

    // 追加新的路径到PATH
    path = addpath + ";" + path;
    QString cmd = "Path=" + path;
    putenv(DataConvertor::CodeGB2312(cmd, "gbk"));
}

void BurnCtrlMcu::StartConnect()
{
    SetChipType(chipModel);
    progressCount = 0;
    myCtrlMap.clear();
    if (!isMultipleBurn) {
        myCtrlMap.insert(0, this);
    } else {
        if (mcuConfig.portName.isEmpty()) {
            return;
        }
    }
    g_isCancel = false;
    if (!OpenDevice()) {
        SetResult(ERR_FAILURE);
        return;
    }

    if (!IsFirstSeriesChip(chipModel)) {
        if (!GetChipIdFun()) {
            if (chipModel == CHIP_TYPE_AUTO) {
                emit SignalSetTips(stringClass.GetAssert(), QString(
                    tr("Chip recognition failed. Reselect the chip and connect.")));
            } else {
                emit SignalSetTips(stringClass.GetAssert(), QString(tr("Failed to obtain chip id. Please reconnect.")));
            }
            CloseDevice(false, true);
            emit SignalSetLog(QString(tr("Connect failed.\n")), false, Qt::red);
            return;
        }
        Sleep(TINER_OUT_GET_CHIPID);
    }

    emit SignalSetLog(QString(tr("Connect successfully.\n")), false, Qt::green);
    if (isMultipleBurn) {
        StartBurn();
    }
}

bool BurnCtrlMcu::OpenDevice()
{
    QString portName = mcuConfig.portName;
    int baudRate = mcuConfig.baudRate;
    int chipModeData = mcuConfig.chipModeData;
    int i2cBaudRate = mcuConfig.i2cBaudRate;
    int spiBaudRate = mcuConfig.spiBaudRate;
    int canBaudRate = mcuConfig.canBaudRate;
    int deviceAddress = mcuConfig.deviceAddress;
    SetSparkLinkParameters(baudRate, i2cBaudRate, deviceAddress, spiBaudRate, canBaudRate);
    SetResult(ERR_WAITING);
    if (transmissionMode == UART_MODE) {
        emit SignalSetLog(QString(tr("Please reset the device, the waiting time is about 30 seconds.\n")),
                          false, Qt::black);
        deviceHander = OpenSerialPort(portName.toLatin1().data(), baudRate, DEFAULT_STOP_BIT, QSerialPort::NoParity);
        if (deviceHander == 0) {
            ShowConnectionSuggestions();
        }
    } else if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        InitJtagTransport();
    } else if (transmissionMode == CAN_MODE) {
        emit SignalSetLog(QString(tr("Please reset the device, the waiting time is about 30 seconds.\n")),
                          false, Qt::black);
        deviceHander = OpenUsb(USB_DEVICES_VID, USB_DEVICES_PID, "-1", Common::USB_TO_CAN, TIMER_OUT_OPEN);
    } else if (transmissionMode == I2C_MODE) {
        emit SignalSetLog(QString(tr("Please reset the device, the waiting time is about 30 seconds.\n")),
                          false, Qt::black);
        deviceHander = OpenUsb(USB_DEVICES_VID, USB_DEVICES_PID, "-1", Common::USB_TO_I2C, TIMER_OUT_OPEN);
    } else if (transmissionMode == SPI_MODE) {
        emit SignalSetLog(QString(tr("Please reset the device, the waiting time is about 30 seconds.\n")),
                          false, Qt::black);
        deviceHander = OpenUsb(USB_DEVICES_VID, USB_DEVICES_PID, "-1", Common::USB_TO_SPI, TIMER_OUT_OPEN);
    } else if (transmissionMode == UART_USB_MODE) {
        emit SignalSetLog(QString(tr("Please reset the device, the waiting time is about 30 seconds.\n")),
                          false, Qt::black);
        deviceHander = OpenUsb(USB_DEVICES_VID, USB_DEVICES_PID, "-1", Common::USB_TO_UART, TIMER_OUT_OPEN);
    }

    return PartOfOpenDevice(chipModeData);
}

bool BurnCtrlMcu::PartOfOpenDevice(int chipModeData)
{
    if (deviceHander == 0) {
        emit SignalSetLog(QString("Connect failed.\n"), false, Qt::red);
        emit SignalSetConnectStatus(false);
        if (chipModeData == CHIP_TYPE_AUTO) {
            emit SignalSetTips(stringClass.GetAssert(), QString(
                tr("Chip recognition failed. Reselect the chip and connect.")));
        } else {
            emit SignalSetTips(stringClass.GetAssert(), QString(tr("Connection failed.")));
        }
        return false;
    }
    if (isMultipleBurn) {
        myCtrlMap.insert(deviceHander, this);
    }
    SetResult(ERR_DOING);
    emit SignalSetConnectStatus(true);
    return true;
}

void BurnCtrlMcu::ShowConnectionSuggestions()
{
    QString portName = mcuConfig.portName;
    emit SignalSetLog(QString("Error: Open serial %1 failed, It may be caused by the following reasons:\n")
              .arg(portName), false, Qt::red);
    emit SignalSetLog(QString(tr("*********************************************************************************"))
                      .append(tr("******************************************************************\n")),
                      false, Qt::red);
    emit SignalSetLog(QString(tr("* 1. The baud rate of the serial port on the mainboard cannot be greater than 115"))
                      .append(tr("200. This restriction does not apply to the USB serial port.     *\n")),
                      false, Qt::red);
    emit SignalSetLog(QString(tr("* 2. check baud rate is correct.                                                 "))
                      .append("                                                                 *\n"),
                      false, Qt::red);
    emit SignalSetLog(QString(tr("* 3. Check whether the port is occupied by other devices or programs.            "))
                      .append("                                                                 *\n"),
                      false, Qt::red);
    emit SignalSetLog(QString(tr("* 4. Check whether the target board is correctly connected to the selected port. "))
                      .append("                                                                 *\n"),
                      false, Qt::red);
    emit SignalSetLog(QString(tr("* 5. Check whether the target board is powered on.                               "))
                      .append("                                                                 *\n"),
                      false, Qt::red);
    emit SignalSetLog(QString(tr("*********************************************************************************"))
                      .append(tr("******************************************************************\n")),
                      false, Qt::red);
}


bool BurnCtrlMcu::InitJtagTransport()
{
    /* init interface file */
    QString interfaceCfg = "";
    if (!InitInterfaceFile(interfaceCfg)) {
        return false;
    }

    /* init target file */
    QString targetCfg = "";
    if (!InitTargetFile(targetCfg)) {
        return false;
    }
    QString jtagPath = Global::GetAppConfigPath() + "\\config_mcu\\JTAG";
    /* open jtag */
    deviceHander = OpenJtag(0, DataConvertor::CodeGB2312(targetCfg, "gbk").data(),
        DataConvertor::CodeGB2312(interfaceCfg, "gbk").data(),
        nullptr, mcuConfig.freq, DEFAULT_PORT, DataConvertor::CodeGB2312(jtagPath, "gbk").data());
    if (deviceHander == 0) {
        emit SignalSetLog(QString(tr("Connecting to Debugger failed. Please check:\n")), false, Qt::red);
        emit SignalSetLog(QString(tr("**************************************************************************\n")),
                          false, Qt::red);
        emit SignalSetLog(QString(tr("* 1.Debugger's Settings, such as debugger model, frequency.              *\n")),
                          false, Qt::red);
        emit SignalSetLog(QString(tr("* 2.Debugger is connected to PC by USB cable.                            *\n")),
                          false, Qt::red);
        emit SignalSetLog(QString(tr("* 3.Debugger is connected to Target board by JTAG/SWD cable.             *\n")),
                          false, Qt::red);
        emit SignalSetLog(QString(tr("* 4.Target Board is powered on.                                          *\n")),
                          false, Qt::red);
        emit SignalSetLog(QString(tr("* 5.All other Debugging and Variable Trace services have been shutdown.  *\n")),
                          false, Qt::red);
        emit SignalSetLog(QString(tr("**************************************************************************\n")),
                          false, Qt::red);
        return false;
    }
    return true;
}

bool BurnCtrlMcu::InitInterfaceFile(QString &interfaceFile)
{
    if (mcuConfig.debugMode == HISPARK_TRACE) {
        interfaceFile = "cmsis-dap.cfg";
    } else if (mcuConfig.debugMode == HISPARK_LINK) {
        if (transmissionMode == JTAG_MODE) {
            interfaceFile = "ft2232h-ftdi-jtag.cfg";
        } else {
            interfaceFile = "ft2232h-ftdi-swd.cfg";
        }
    } else {
        printf("error: The small board model: %d is not supported!\n", mcuConfig.debugMode);
        return false;
    }

    return true;
}

bool BurnCtrlMcu::InitTargetFile(QString &targetFile)
{
    QString jtagFile = mcuJtagSwdPath.targetFileJTAG;
    QString swdFile = mcuJtagSwdPath.targetFileSWD;
    if (isWriteInfo) {
        isWriteInfo = false;
        jtagFile = mcuJtagSwdPath.targetFileInfoJTAG;
        swdFile = mcuJtagSwdPath.targetFileInfoSWD;
    }
    if (transmissionMode == JTAG_MODE) {
        targetFile = jtagFile;
    } else {
        targetFile = swdFile;
    }
    return true;
}

bool BurnCtrlMcu::IsFirstSeriesChip(int chipMode) const
{
    if (chipMode == CHIP_TYPE_MCU1 || chipMode == CHIP_TYPE_MCU2 || chipMode == CHIP_TYPE_MCU3) {
        return true;
    }
    return false;
}

bool BurnCtrlMcu::GetChipIdFun()
{
    QByteArray chipInfo;
    chipInfo.clear();
    QString uploadPath = Global::GetAppConfigPath() + "\\config_mcu\\temp\\uploadFileTemp.bin";
    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        int uploadCode = UploadFile(deviceHander, mcuChipIdInfo.chipIDAddress, mcuChipIdInfo.chipIDSize,
            DataConvertor::CodeGB2312(uploadPath, "gbk").data());
        if (uploadCode == 0) {
            emit SignalSetTips(stringClass.GetAssert(), QString(tr("Readback failed.")));
            return false;
        }
        int code = OnlyReadFile(uploadPath, chipInfo);
        if (!ReadFileShowLog(code, uploadPath)) {
            return false;
        }

        for (int i = 0; i < chipInfo.size(); i++) {
            chipIdAttr.chipId |= (static_cast<uint>(chipInfo[i]) & 0xff) << (i * BYTE_LEN);
        }
    } else {
        int length = sizeof(ChipIdAttr);
        if (GetChipId(reinterpret_cast<BYTE *>(&chipIdAttr), length) == 0) {
            return false;
        }
        chipIdAttr.chipId = htonl(chipIdAttr.chipId);
    }
    QString chipName = "";
    emit SignalChangeChipModeByChipId(chipName, chipIdAttr.chipId);
    return true;
}

int BurnCtrlMcu::OnlyReadFile(QString szFile, QByteArray &data) const
{
    QFile file(szFile);
    if (!file.exists(szFile)) {
        return FILE_NULL;
    }
    if (!file.open(QIODevice::ReadOnly)) {
        return FILE_OPEN_FAIL;
    }

    data = file.readAll();
    file.close();
    return SUCCESS;
}

bool BurnCtrlMcu::ReadFileShowLog(int code, QString filePath)
{
    if (code == FILE_NULL) {
        emit SignalSetLog(QString(tr("The %1 file does not exist. Please check the file path.")).arg(filePath),
                          false, Qt::red);
        return false;
    }
    if (code == FILE_OPEN_FAIL) {
        emit SignalSetLog(QString(tr("Failed to read %1.")).arg(filePath) +
            QString(tr(" Check whether you have the read permission on the file or whether the file is open.\n")),
            false, Qt::red);
        return false;
    }
    return true;
}

int BurnCtrlMcu::GetChipId(BYTE *recevie, int recvLen) const
{
    if (deviceHander == 0) {
        return 0;
    }
    BYTE cmdframe[1] = {0};
    cmdframe[0] = GET_CHIPID_CMD;
    int code = SendCmd(deviceHander, reinterpret_cast<BYTE *>(cmdframe), sizeof(cmdframe),
                       recevie, recvLen, TIMER_OUT_FRAME);
    return code;
}

void BurnCtrlMcu::McuReopen()
{
    if ((transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE)) {
        CloseDevice(true, false);
    }
    SetChipType(chipModel);

    if ((transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE)) {
        Sleep(REOPEN_SERIAL_TIMEOUT);
        if (!OpenDevice()) {
            emit SignalSetTips(stringClass.GetAssert(), QString("Reconnect failed."));
            return;
        }
    }
}

void BurnCtrlMcu::CloseDevice(bool isConnectState, bool isUIState)
{
    Q_UNUSED(isUIState);
    if (deviceHander == 0) {
        return;
    }
    Cancel(deviceHander);
    Sleep(WAIT_DEVICE_CLOSE);
    if (DestroyDevice(deviceHander) == 0) {
        return;
    }
    myCtrlMap.remove(deviceHander);
    deviceHander = 0;
    emit SignalSetConnectStatus(isConnectState);
}

void BurnCtrlMcu::InitBurnCmdByArray(const QMap<int, BIN_INFO> &infoArray)
{
    if (infoArray.size() == 1 && infoArray[0].fileName.isEmpty()) {
        isAllinone = false;
    } else {
        isAllinone = true;
    }
    this->infoArray.clear();
    this->infoArray = infoArray;
    for (auto it = infoArray.begin(); it != infoArray.end(); it++) {
        this->infoArray[it.key()] = it.value();
        if (it.value().type == IMAGE_TYPE::IMAGE_TYPE_LOADER) {
            loadfile.path = it.value().path;
            loadfile.fileName = it.value().fileName;
            loadfile.fileIndex = it.value().fileIndex;
            loadfile.fileSize = it.value().fileSize;
            loadfile.burnAddr = it.value().burnAddr;
            loadfile.burnSize = it.value().burnSize;
            loadfile.type = it.value().type;
        }

        if (it.value().type == IMAGE_TYPE::IMAGE_TYPE_NORMAL) {
            eflashfile.path = it.value().path;
            eflashfile.fileName = it.value().fileName;
            eflashfile.fileIndex = it.value().fileIndex;
            eflashfile.fileSize = it.value().fileSize;
            eflashfile.burnAddr = it.value().burnAddr;
            eflashfile.burnSize = it.value().burnSize;
            eflashfile.type = it.value().type;
        }
    }
    QFile file(eflashfile.path);
    char *pBurnFile = new char[file.size()]{ 0 };
    if (pBurnFile == nullptr) {
        return;
    }
    file.open(QIODevice::ReadOnly);
    int ret = file.read(pBurnFile, DataConvertor::Str2U32DecHex(eflashfile.fileSize));
    GetFileData(info, eflashfile, pBurnFile, DataConvertor::Str2U32DecHex(eflashfile.fileSize));
    file.close();
    delete []pBurnFile;
    pBurnFile = nullptr;
}

U32 BurnCtrlMcu::StartBurn()
{
    if (deviceHander == 0) {
        emit SignalSetTips(stringClass.GetAssert(), QString(tr("Please connect the device first.")));
        BurnFileFailed();
        return ERR_FAILURE;
    }
    progressCount = 0;
    g_isCancel = false;
    QString partitionFile = burnPath;
    QFile file(partitionFile);
    if (!file.open(QIODevice::ReadOnly)) {
        emit SignalSetTips(stringClass.GetAssert(), stringClass.GetNotReadAgainTips().arg(partitionFile));
        return ERR_FAILURE;
    }
    file.close();
    QString burnAddress = eflashfile.burnAddr;
    if (burnAddress.isEmpty()) {
        emit SignalSetTips(stringClass.GetAssert(), tr("The burn address cannot be empty."));
        BurnFileFailed();
        return ERR_FAILURE;
    }
    DWORD fileAddress = DataConvertor::Str2U32DecHex(burnAddress);
    if (fileAddress % BURN_START_ADDRESS_BASE_BIT != 0) {
        emit SignalSetTips(stringClass.GetAssert(), tr("The burning address must be a multiple of %1.").
                           arg(BURN_START_ADDRESS_BASE_BIT));
        BurnFileFailed();
        return ERR_FAILURE;
    }
    StartBurnImage(burnPath, fileAddress, DataConvertor::Str2U32DecHex(eflashfile.fileSize));
    return 0;
}

void BurnCtrlMcu::BurnFileFailed()
{
    CloseDevice(false, true);
    SetResult(ERR_FAILURE);
    emit SignalBurnFileFailed();
    return;
}

/* Start burning */
void BurnCtrlMcu::StartBurnImage(QString fileName, DWORD burnAddress, DWORD fileSize)
{
    QFileInfo file(fileName);
    DWORD burnSize = file.size();
    if (!CheckFlashAddressLegitimacy(burnAddress, fileSize, mainStartAddrInfo.mainStartAddress,
                                     mainStartAddrInfo.mainSize * CARRY)) {
        emit SignalSetTips(QString(stringClass.GetAssert()), QString(tr("The burn address must not be less than 0x%1,"
                                                           " and the maximum burn address must not exceed 0x%2."))
                           .arg(QString::number(mainStartAddrInfo.mainStartAddress, 0x10))
                           .arg(QString::number(mainStartAddrInfo.mainStartAddress +
                                                mainStartAddrInfo.mainSize * CARRY, 0x10)));
        BurnFileFailed();
        return;
    }

    if (!DownloadFile(fileName, burnAddress, burnSize)) {
        BurnFileFailed();
        return;
    }
    emit SignalSetLog(QString(tr("\nBurned successfully.\n")), false, Qt::green);
    /* Readback verification after loading */
    if (!isAllinone && !VerifyImage(burnAddress, burnSize)) {
        BurnFileFailed();
        return;
    }

    /* Run directly after loading and disconnect */
    if (!isAllinone && !RunTarget(burnAddress)) {
        BurnFileFailed();
        return;
    }
    if (isAllinone) {
        CloseDevice(false, true);
    }
    SetResult(ERR_SOP_SUCCESS);
    if (isMultipleBurn) {
        emit SignalSetProgressValue(SHRT_MAX);
    }

    emit SignalBurnFileFinish();
}

bool BurnCtrlMcu::VerifyImage(DWORD burnAddress, DWORD burnSize)
{
    if (isReadBack) {
        if (!CheckFile(burnAddress, burnSize)) {
            emit SignalSetLog(QString(tr("Check failed.\n")), false, Qt::red);
            return false;
        }
        emit SignalSetLog(QString(tr("\nCheck successfully.\n")), false, Qt::green);
    }
    return true;
}

bool BurnCtrlMcu::CheckFile(DWORD burnAddress, DWORD burnSize)
{
    QByteArray data;
    QString partitionFile = Global::GetAppConfigPath() + "\\config_mcu\\temp\\uploadFileTemp.bin";
    QString hexToBinPath = Global::GetAppConfigPath() + "\\config_mcu\\temp\\hexToBin.bin";
    if (!IsFirstSeriesChip(chipModel)) {
        if (burnPath.toLower().lastIndexOf(".hex") != -1) {
            burnPath = hexToBinPath;
        }
        int burnPathCode = OnlyReadFile(burnPath, info);
        if (!ReadFileShowLog(burnPathCode, burnPath)) {
            return false;
        }
    }
    int uploadCode = ReadFlashData(burnAddress, burnSize, partitionFile);
    if (uploadCode == 0) {
        emit SignalSetLog(QString("\nFailed to read back.\n"), false, Qt::red);
        return false;
    }
    int roundUp = (info.size() % UPLOAD_SLEEP) > 0 ? 1 : 0;
    Sleep(((info.size() / UPLOAD_SLEEP) + roundUp) * TIMER_OUT_WRITE_FILE);
    QString uploadPath = partitionFile;
    int code = OnlyReadFile(uploadPath, data);
    if (!ReadFileShowLog(code, uploadPath)) {
        return false;
    }
    if (data.size() != info.size()) {
        return false;
    }
    for (int i = 0; i < info.size(); i++) {
        if (data[i] != info[i]) {
            return false;
        }
    }

    return true;
}

DWORD BurnCtrlMcu::ReadFlashData(DWORD readAddress, int readSize, QString readFilePath)
{
    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        DWORD flashBlock = 0;
        DWORD baseAddress = mainStartAddrInfo.mainStartAddress;
        if (readAddress >= infoStartAddress) {
            flashBlock = 1;
            baseAddress = infoStartAddress;
        }
        DWORD blockOffset = readAddress - baseAddress;
        return ReadFlashToFile(deviceHander, flashBlock, DataConvertor::CodeGB2312(readFilePath, "gbk").data(),
            blockOffset, readSize);
    } else {
        return UploadFile(deviceHander, readAddress, readSize, DataConvertor::CodeGB2312(readFilePath, "gbk").data());
    }
}

bool BurnCtrlMcu::DownloadFile(QString fileName, DWORD burnAddress, DWORD burnSize)
{
    if (IsFirstSeriesChip(chipModel)) {
        if (!BurnLoadTarget(fileName, burnAddress, burnSize)) {
            return false;
        }
    } else {
        if (!BurnTarget(fileName, burnAddress, burnSize)) {
            return false;
        }
    }
    return true;
}

bool BurnCtrlMcu::BurnLoadTarget(QString fileName, DWORD burnAddress, DWORD burnSize)
{
    if (isAllinone || transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        if (DownloadAllinoneBin(deviceHander, DataConvertor::CodeGB2312(fileName, "gbk").data(), burnAddress) == 0) {
            emit SignalSetLog(QString(tr("Burned failed.\n")), false, Qt::red);
            emit SignalSetTips(stringClass.GetAssert(), tr("File burning failed."));
            return false;
        }
    } else {
        if (!Burnload(loaderFile, loaderSize)) {
            emit SignalSetLog(QString(tr("Failed to upgrade the loader.\n")), false, Qt::red);
            return false;
        }

        if (DownloadToFlash(deviceHander, DataConvertor::CodeGB2312(fileName, "gbk").data(), nullptr, nullptr,
                            burnAddress, burnSize, 0, 0) == 0) {
            emit SignalSetLog(QString(tr("Burned failed.\n")), false, Qt::red);
            return false;
        }
    }
    return true;
}

bool BurnCtrlMcu::Burnload(QString loadFilePath, DWORD burnAddress)
{
    QFile file(loadFilePath);
    if (!file.open(QIODevice::ReadOnly)) {
        emit SignalSetLog(QString("Open %1 failed.\n").arg(loadFilePath), false, Qt::red);
        return false;
    }
    file.close();
    if (BootDownload(deviceHander, DataConvertor::CodeGB2312(loadFilePath, "gbk").data(),
                     burnAddress, 0, nullptr, nullptr) == 0) {
        emit SignalSetLog(QString("Burned loader failed.\n"), false, Qt::red);
        return false;
    }
    return true;
}

bool BurnCtrlMcu::BurnTarget(QString fileName, DWORD burnAddress, DWORD burnSize)
{
    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        if (DownloadAllinoneBin(deviceHander, DataConvertor::CodeGB2312(fileName, "gbk").data(), burnAddress) == 0) {
            emit SignalSetLog(QString(tr("Burned failed.\n")), false, Qt::red);
            emit SignalSetTips(stringClass.GetAssert(), tr("File burning failed."));
            return false;
        }
    } else {
        if (!(burnAddress >= sramAddrInfo.sramStartAddress && burnAddress <= sramAddrInfo.sramEndAddress) &&
            Erase(burnAddress, burnSize) == 0) {
            emit SignalSetLog(QString(tr("Erase failed.\n")), false, Qt::red);
            return false;
        }
        if (DownloadToFlash(deviceHander, DataConvertor::CodeGB2312(fileName, "gbk").data(),
                            nullptr, nullptr, burnAddress, 0, 0, 0) == 0) {
            emit SignalSetLog(QString(tr("Burned failed.\n")), false, Qt::red);
            return false;
        }
    }
    return true;
}

int BurnCtrlMcu::Erase(unsigned long long address, int length)
{
    if (deviceHander == 0 || length < 1) {
        return 0;
    }

    if (eraseBaseNum == 0) {
        eraseBaseNum = 1;
    }
    length = length % eraseBaseNum == 0 ? length : ceil((length / eraseBaseNum) + 1) * eraseBaseNum;
    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        return EraseFlash(deviceHander, address, length);
    } else {
        for (int i = 0; i <= length / FLASH_128K; i++) {
            uint len = FLASH_128K;
            if (length -  (FLASH_128K * i) < FLASH_128K) {
                len = length - (FLASH_128K * i);
            }
            if (len == 0) {
                break;
            }
            BYTE cmdframe[ERASE_CMD_LEN] = { 0 };
            cmdframe[0] = ERASECMD;
            cmdframe[1] = ((address + (FLASH_128K * i)) >> BIT_24) & 0xff;
            cmdframe[2] = ((address + (FLASH_128K * i)) >> BIT_16) & 0xff; // 2
            cmdframe[3] = ((address + (FLASH_128K * i)) >> BIT_8) & 0xff; // 3
            cmdframe[4] = (address + (FLASH_128K * i)) & 0xff; // 4
            cmdframe[5] = (len >> BIT_24) & 0xff; // 5
            cmdframe[6] = (len >> BIT_16) & 0xff; // 6
            cmdframe[7] = (len >> BIT_8) & 0xff; // 7
            cmdframe[8] = len & 0xff; // 8
            if (SendCmd(deviceHander, reinterpret_cast<BYTE *>(cmdframe), sizeof(cmdframe), nullptr, 0,
                        TIMER_OUT_FRAME) == 0) {
                return 0;
            }
        }
        return 1;
    }
}

bool BurnCtrlMcu::CheckFlashAddressLegitimacy(DWORD address, DWORD size,
    DWORD baseAddress, DWORD maxSize) const
{
    if (address < baseAddress) {
        return false;
    }
    if (address + size > baseAddress + maxSize) {
        return false;
    }
    return true;
}


void BurnCtrlMcu::SetTextEditReceivePrint(long long handle, const char* comNum,
                                          const char* szInfo, const char* deBugszInfo, BOOL flag)
{
    Q_UNUSED(handle);
    if (myCtrlMap.size() == 1) {
        myCtrlMap.first()->OutputString(QString::fromLatin1(szInfo));
        myCtrlMap.first()->OutputString(QString::fromLatin1(deBugszInfo));
    }

    Q_UNUSED(comNum);
    Q_UNUSED(flag);
}

void BurnCtrlMcu::SetProgress(long long handle, int progress)
{
    auto it = myCtrlMap.find(handle);
    if (it != myCtrlMap.end()) {
        double pro = 0;
        if (it.value()->isAllinone || !it.value()->IsFirstSeriesChip(it.value()->chipModel)) {
            if (it.value()->infoArray.size() < it.value()->progressCount) {
                return;
            }
            SetProgressPart(it.value(), pro, progress);
        } else {
            if (it.value()->progressCount > 2) { // 2
                return;
            }
            if (it.value()->progressCount == 0) {
                pro = progress / 5; // 5
            } else if (it.value()->infoArray.begin() + (it.value()->progressCount - 1) == it.value()->infoArray.end()) {
                pro = PRO_CALL_BACK + progress / 5; // 5
            } else if (!it.value()->isReadBack) {
                pro = PRO_20 + progress * 0.8; // 0.8
            } else {
                pro = PRO_20 + progress * 0.6; // 0.6
            }
            qDebug() << "pro:" << pro;
        }
        if (progress - PERCENTAGE < 0.000001) { // 0.000001
            it.value()->progressCount++;
        }
        if (pro > PRO_99) {
            pro = PRO_99;
        }
        double pos = (pro / PERCENTAGE) * SHRT_MAX;
        emit it.value()->SignalSetProgressValue(pos);
    } else if (myCtrlMap.size() == 1) {
        myCtrlMap.first()->SignalSetProgressValue(progress);
    }
}

void BurnCtrlMcu::SetProgressPart(BurnCtrlMcu *&mcu, double &pro, int progress)
{
    UINT sum = 0;
    UINT fileSizeSum = 0;
    if (mcu->infoArray.begin() + mcu->progressCount == mcu->infoArray.end()) {
        if (!mcu->isReadBack) {
            return;
        } else {
            pro = PRO_CALL_BACK + progress / 5; // 5
        }
    } else {
        UINT fileSize = DataConvertor::Str2U32DecHex((mcu->infoArray.begin() +
                                                      mcu->progressCount)->fileSize);
        for (auto it1 = mcu->infoArray.begin(); it1 != mcu->infoArray.end(); it1++) {
            sum += DataConvertor::Str2U32DecHex(it1.value().fileSize);
        }
        for (auto it1 = mcu->infoArray.begin(); (it1 != mcu->infoArray.end()) &&
             (it1 != mcu->infoArray.begin() + mcu->progressCount); it1++) {
            fileSizeSum += DataConvertor::Str2U32DecHex(it1.value().fileSize);
        }
        if (!mcu->isReadBack) {
            pro = (static_cast<double>(fileSizeSum) + fileSize * progress / PERCENTAGE) / sum * PERCENTAGE;
        } else {
            pro = (static_cast<double>(fileSizeSum) + fileSize * progress / PERCENTAGE) / sum * PRO_CALL_BACK;
        }
    }
}

bool BurnCtrlMcu::RunTarget(DWORD burnAddress)
{
    int offset = RUN_OFFSET_ADDRESS;
    if (isRun) {
        if (burnAddress >= sramAddrInfo.sramStartAddress && burnAddress <= sramAddrInfo.sramEndAddress) {
            offset = 0;
        }
        if (Go(burnAddress + offset) == 0) {
            emit SignalSetLog(QString(tr("Running failed.\n")), false, Qt::red);
            return false;
        }
        CloseDevice(false, true);
        emit SignalSetLog(QString(tr("Running successfully.\n")), false, Qt::green);
    }

    return true;
}

int BurnCtrlMcu::Go(unsigned long long address) const
{
    if (deviceHander == 0) {
        return 0;
    }
    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        if (Reset(deviceHander) == FALSE) {
            return 0;
        }
        return 1;
    }
    BYTE cmdframe[GO_CMD_LEN] = {0};
    cmdframe[0] = GOCMD;
    cmdframe[1] = (address >> BIT_24) & 0xff;
    cmdframe[2] = (address >> BIT_16) & 0xff; // 2
    cmdframe[3] = (address >> BIT_8) & 0xff; // 3
    cmdframe[4] = address & 0xff; // 4
    return SendCmd(deviceHander, reinterpret_cast<BYTE *>(cmdframe), sizeof(cmdframe), nullptr, 0, TIMER_OUT_FRAME);
}

void BurnCtrlMcu::SlotExport(WPARAM data, LPARAM len)
{
    ExportInfo *mInfo = reinterpret_cast<ExportInfo *>(data);
    QString readFilePath = mInfo->path;
    unsigned long readAddress = mInfo->addr;
    unsigned long readSize = mInfo->len;
    emit SignalSetLog(QString(tr("Start reading.\n")), false, Qt::black);
    if (IsFirstSeriesChip(chipModel) && transmissionMode != JTAG_MODE && transmissionMode != SWD_MODE) {
        if (!Burnload(loaderFile, loaderSize)) {
            emit SignalSetLog(QString(tr("Failed to upgrade the loader.\n")), false, Qt::red);
            emit SignalExportfail();
            return;
        }
    }
    /* Invoke the read method */
    int uploadCode = ReadFlashData(readAddress, readSize, readFilePath);
    if (uploadCode == 0) {
        emit SignalSetLog(QString(tr("Read failed.\n")), false, Qt::red);
        emit SignalExportfail();
        return;
    }
    if (IsFirstSeriesChip(chipModel)) {
        ResetDrvice();
        CloseDevice(false, true);
    }
    emit SignalSetLog(QString(tr("Read successfully.\n")), false, Qt::green);
    emit SignalExportSuccess();
}

int BurnCtrlMcu::ResetDrvice() const
{
    if (deviceHander == 0) {
        return 0;
    }
    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        return SendBytes(deviceHander, reinterpret_cast<BYTE *>(JLINK_GO_CMD.toLatin1().data()), JLINK_GO_CMD.size());
    }
    BYTE cmdframe[1] = {0};
    cmdframe[0] = RESETCMD;
    return SendCmd(deviceHander, reinterpret_cast<PBYTE>(cmdframe), 1, nullptr, 0, TIMER_OUT_FRAME);
}

void BurnCtrlMcu::SetLoaderInfo(QString mLoaderFile, int mLoaderSize)
{
    this->loaderFile = mLoaderFile;
    this->loaderSize = mLoaderSize;
}

void BurnCtrlMcu::SlotEraseMemory(WPARAM data, LPARAM len)
{
    ExportInfo *mInfo = reinterpret_cast<ExportInfo *>(data);
    unsigned long eraseAddress = mInfo->addr;
    unsigned long eraseSize = mInfo->len;
    emit SignalSetLog(QString("Start erase %1 %2.\n").arg(QString::number(eraseAddress, HEX))
              .arg(QString::number(eraseSize, HEX)), false, Qt::black);
    int eraseCode = Erase(eraseAddress, eraseSize);
    if (eraseCode == 0) {
        emit SignalSetLog(QString("Erase failed.\n"), false, Qt::red);
        return;
    }
    emit SignalSetLog(QString("Erase successfully.\n"), false, Qt::green);
}

void BurnCtrlMcu::ResetUIAndFSM()
{
    CloseDevice(false, true);
}

void BurnCtrlMcu::SetTotalDigits(int mTotalDigits)
{
    this->totalDigits = mTotalDigits;
}

void BurnCtrlMcu::SlotGetParameter()
{
    if (IsFirstSeriesChip(chipModel) && transmissionMode != JTAG_MODE && transmissionMode != SWD_MODE) {
        return;
    }

    if (!GetParameterInfoList()) {
        return;
    }
}

bool BurnCtrlMcu::GetParameterInfoList()
{
    int parameterLength = totalDigits / BYTE_LEN + ((totalDigits % BYTE_LEN) != 0 ? 1 : 0);
    QString uploadPath = Global::GetAppConfigPath() + "\\config_mcu\\temp\\uploadFileTemp.bin";
    if (ReadFlashData(parameterStartAddress, parameterLength, uploadPath) == 0) {
        emit SignalSetLog(QString(tr("Get parameter failed.\n")), false, Qt::red);
        return false;
    }
    chipParameterList.clear();
    /* assign a value to m_parameterInfoList. */
    int code = OnlyReadFile(DataConvertor::CodeGB2312(uploadPath, "UTF-8").data(), chipParameterList);
    if (!ReadFileShowLog(code, uploadPath)) {
        emit SignalSetLog(QString(tr("Get parameter failed.\n")), false, Qt::red);
        return false;
    }

    emit SignalSetParameterInfoList(chipParameterList);
    return true;
}

void BurnCtrlMcu::SetParamStartAddress(ulong mParameterStartAddress)
{
    this->parameterStartAddress = mParameterStartAddress;
}

void BurnCtrlMcu::SetProtectionPara(ParameterInfo mInfo)
{
    protectionPara = mInfo;
}

void BurnCtrlMcu::SlotSetParameter()
{
    if (!UnprotectParameterArea()) {
        return;
    }

    if (!StartSetParameter()) {
        return;
    }

    /* is reset */
    if (isParameterReset) {
        if (ResetDrvice() == 0) {
            emit SignalSetTips(QString(stringClass.GetInformation()), QString(tr("Reset failed.")));
            return;
        }
        CloseDevice(false, true);
    }
    emit SignalSetLog(QString(tr("Set parameter successfully.\n")), false, Qt::green);
}

bool BurnCtrlMcu::StartSetParameter()
{
    if (chipModel != CHIP_TYPE_MCU5) {
        return DirectSetParameter();
    } else {
        return SetParameterWhenUnlock();
    }
}

bool BurnCtrlMcu::SetParameterWhenUnlock()
{
    if (IsUnlockInfo() && Islock()) {
        int parameterLength = totalDigits / BYTE_LEN + ((totalDigits % BYTE_LEN) != 0 ? 1 : 0);
        parameterLength = (parameterLength % eraseBaseNum) != 0 ?
                    ((parameterLength / eraseBaseNum) + 1) * eraseBaseNum : parameterLength;
        Erase(parameterStartAddress, parameterLength);
    }
    SetIsEraseBeforeBurn(0);
    if (!SaveParameterWhenUnlock()) {
        SetIsEraseBeforeBurn(1);
        return false;
    }
    SetIsEraseBeforeBurn(1);
    return true;
}

bool BurnCtrlMcu::SaveParameterWhenUnlock()
{
    DWORD startAddress = 0;
    int parameterLen = 0;
    QFile fileBits(parameterFilePath);
    for (int i = 0; i < mapBuf.size(); i++) {
        if (!mapBuf[i].isChange) {
            if (parameterLen == 0) {
                continue;
            }
            fileBits.close();
            if (!BurnFile(parameterFilePath, startAddress, parameterLen)) {
                emit SignalSetTips(QString(stringClass.GetInformation()), QString(tr("Failed to set parameters.")));
                return false;
            }
            parameterLen = 0;
        } else {
            bool isOpenFile = true;
            if (!fileBits.isOpen()) {
                isOpenFile = fileBits.open(QIODevice::WriteOnly);
                startAddress = mapBuf[i].startAddr;
            }
            if (!isOpenFile) {
                emit SignalSetTips(QString(stringClass.GetInformation()),
                                   stringClass.GetNotWriteAgainTips().arg(parameterFilePath));
                return false;
            }
            parameterLen += mapBuf[i].buff.size();
            fileBits.write(mapBuf[i].buff.data(), mapBuf[i].buff.size());
        }
    }
    if (fileBits.isOpen()) {
        fileBits.close();
    }

    if (parameterLen > 0) {
        if (!BurnFile(parameterFilePath, startAddress, parameterLen)) {
            emit SignalSetTips(QString(stringClass.GetInformation()), QString(tr("Failed to set parameters.")));
            return false;
        }
    }
    return true;
}

bool BurnCtrlMcu::BurnFile(QString filePath, DWORD address, int length) const
{
    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        if (DownloadAllinoneBin(deviceHander, DataConvertor::CodeGB2312(filePath, "gbk").data(),
            address) == 0) {
            return false;
        }
    } else {
        if (DownloadToFlash(deviceHander, DataConvertor::CodeGB2312(filePath, "gbk").data(), nullptr,
            nullptr, address, length, 0, 0) == 0) {
            return false;
        }
    }
    return true;
}

bool BurnCtrlMcu::IsUnlockInfo()
{
    for (int i = 0; i < infoUnlockList.size(); i++) {
        if (infoUnlockList[i].oldValue != infoUnlockList[i].defaultValue) {
            return false;
        }
    }
    return true;
}

bool BurnCtrlMcu::Islock() const
{
    for (int i = 0; i < mapBuf.size(); i++) {
        if (mapBuf[i].lock) {
            return false;
        }
    }
    return true;
}

bool BurnCtrlMcu::DirectSetParameter()
{
    /* get parameter bin file */
    if (GetParameterData() != SUCCESS) {
        emit SignalSetTips(QString(stringClass.GetInformation()),
                           stringClass.GetNotWriteAgainTips().arg(parameterFilePath));
        return false;
    }

    if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
        if (DownloadAllinoneBin(deviceHander, DataConvertor::CodeGB2312(parameterFilePath, "gbk").data(),
            parameterStartAddress) == 0) {
            return false;
        }
    } else {
        int parameterLen = totalDigits / BYTE_LEN + ((totalDigits % BYTE_LEN) != 0 ? 1 : 0);
        if (Erase(parameterStartAddress, parameterLen) == 0) {
            emit SignalSetTips(QString(stringClass.GetInformation()), QString(tr("Erase failed.")));
            return false;
        }

        /* set parater */
        if (DownloadToFlash(deviceHander, DataConvertor::CodeGB2312(parameterFilePath, "gbk").data(), nullptr, nullptr,
            parameterStartAddress, parameterLen, 0, 0) == 0) {
            emit SignalSetTips(QString(stringClass.GetInformation()), QString(tr("Failed to set parameters.")));
            return false;
        }
    }
    return true;
}

int BurnCtrlMcu::GetParameterData()
{
    QBitArray bits(totalDigits);
    int offset = 0;
    foreach(const ParameterInfo mInfo, parameterInfoList) {
        for (uint i = 0; i < mInfo.digitsNum; i++) {
            bits.setBit(offset + i, (mInfo.newValue >> i) & 0x1);
        }
        offset += mInfo.digitsNum;
    }
    QByteArray a = DataConvertor::Bits2Bytes(bits);
    QFile fileBits(parameterFilePath);
    if (!fileBits.open(QIODevice::WriteOnly)) {
        return FAIL;
    }

    fileBits.write(a.data(), a.size());
    fileBits.close();
    return SUCCESS;
}

bool BurnCtrlMcu::UnprotectParameterArea()
{
    if (protectionPara.oldValue != protectionPara.defaultValue
        && protectionPara.newValue == protectionPara.defaultValue) {
        if (transmissionMode == JTAG_MODE || transmissionMode == SWD_MODE) {
            emit SignalSetTips(QString(stringClass.GetInformation()), QString(
                tr("JTAG/SWD cannot be unprotected, please choose other connection methods.")));
            return false;
        }

        /* unprotected */
        if (Unprotected() == 0) {
            emit SignalSetTips(QString(stringClass.GetInformation()), QString(tr("Unprotection failed.")));
            return false;
        }

        Sleep(mainStartAddrInfo.mainSize * 1000); // 1000
        /* reset again Connect */
        if (ResetDrvice() == 0) {
            emit SignalSetTips(QString(stringClass.GetInformation()), QString(tr("Reset failed.")));
            return false;
        }

        if (!ReOpen()) {
            return false;
        }
    }
    return true;
}

bool BurnCtrlMcu::ReOpen()
{
    CloseDevice(true, false);
    Sleep(REOPEN_SERIAL_TIMEOUT);
    if (!OpenDevice()) {
        return false;
    }
    return true;
}

int BurnCtrlMcu::Unprotected() const
{
    if (deviceHander == 0) {
        return 0;
    }
    if (isInfoPart) {
        const int infoAreaNum = iInfoArea;
        BYTE cmdframe[3] = { 0 };
        cmdframe[0] = CMD_UP_3066_FLASH; /* 0: Unprotect command */
        cmdframe[1] = infoAreaNum;       /* 1: info index */
        cmdframe[2] = 0xFF;              /* 2: Protection value */
        return SendCmd(deviceHander, reinterpret_cast<BYTE *>(cmdframe), sizeof(cmdframe), nullptr, 0,
            TIMER_OUT_FRAME);
    } else {
        BYTE cmdframe[1] = { 0 };
        cmdframe[0] = UNPROTECTCMD;
        return SendCmd(deviceHander, reinterpret_cast<BYTE *>(cmdframe), sizeof(cmdframe), nullptr, 0,
            TIMER_OUT_FRAME);
    }
}

void BurnCtrlMcu::SetIsInfoPart(bool isPart)
{
    isInfoPart = isPart;
}

void BurnCtrlMcu::SetInfoArea(int infoArea)
{
    iInfoArea = infoArea;
}

void BurnCtrlMcu::SetParameterInfoList(const QList<ParameterInfo> &mParameterInfoList,
                                       const QList<ParameterInfo> &mInfoUnlockList,
                                       const QList<ParameterBuff> &mMapBuf)
{
    this->parameterInfoList = mParameterInfoList;
    this->infoUnlockList = mInfoUnlockList;
    this->mapBuf = mMapBuf;
}

void BurnCtrlMcu::SetParameterSaveInfo(const QString &mParameterFilePath, ulong mEraseBaseNum)
{
    this->parameterFilePath = mParameterFilePath;
    this->eraseBaseNum = mEraseBaseNum;
}

void BurnCtrlMcu::SetIsParameterReset(bool isReset)
{
    isParameterReset = isReset;
}

void BurnCtrlMcu::BurnCtrlCancel()
{
    g_isCancel = true;
}

int BurnCtrlMcu::CancelBurn()
{
    return g_isCancel;
}

bool BurnCtrlMcu::GetFileData(QByteArray &data, BIN_INFO &binInfo,
    const char *pBurnFile, DWORD fileLen) const
{
    data.resize(DataConvertor::Str2U32DecHex(binInfo.fileSize) & 0xffffffff);
    for (unsigned int j = 0; j < (DataConvertor::Str2U32DecHex(binInfo.fileSize) & 0xffffffff);
         j++) {
        if (j + DataConvertor::Str2U32DecHex(binInfo.fileIndex) >= fileLen) {
            break;
        }
        data[j] =  pBurnFile[j + DataConvertor::Str2U32DecHex(binInfo.fileIndex)];
    }
    return true;
}

void BurnCtrlMcu::SetResult(unsigned int result)
{
    this->result = result;
    AddLog(stringClass.GetIdsString32845() + QString::number(this->result));
    NotifyState(NOTIFY_TYPE_RESULT, this->result);
    ShowResult();
}
